﻿using UnityEngine;
using System.Collections;

public class PerlinNoise
{
    // PERSITENCE is always 0.5f
    public const int NUMBER_OF_OCTAVES = 1;

    public static float Noise_1D(int x)
    {
        x = (x << 13) ^ x;
        return (1.0f - ((x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0f);
    }
    public static float Noise_2D(int x, int y)
    {
        int n = x + y * 57;
        n = (n << 13) ^ n;
        return (1.0f - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0f);
    }
    public static float Noise_3D(int x, int y, int z)
    {
        int noise_n = x + y * 57 + z * 71;
        noise_n = (noise_n << 13) ^ noise_n;
        return (1.0f - (((noise_n * (noise_n * noise_n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0f));
    }

    private static float SmoothNoise_1D(int x)
    {
        return Noise_1D(x) * 0.5f + Noise_1D(x - 1) * 0.25f + Noise_1D(x + 1) * 0.25f;
    }
    private static float SmoothNoise_2D(int x, int y)
    {
        float corners = (Noise_2D(x - 1, y - 1) + Noise_2D(x + 1, y - 1) + Noise_2D(x - 1, y + 1) + Noise_2D(x + 1, y + 1)) / 16.0f;
        float sides = (Noise_2D(x - 1, y) + Noise_2D(x + 1, y) + Noise_2D(x, y - 1) + Noise_2D(x, y + 1)) / 8.0f;
        float center = Noise_2D(x, y) / 4;
        return corners + sides + center;
    }
    private static float SmoothNoise_3D(int x, int y, int z)
    {
        float center = Noise_3D(x, y, z) * 0.125f;
        float faces = (Noise_3D(x + 1, y, z) + Noise_3D(x - 1, y, z) + Noise_3D(x, y + 1, z) + Noise_3D(x, y - 1, z) + Noise_3D(x, y, z + 1) + Noise_3D(x, y, z - 1)) * 0.0625f;
        float sides = (Noise_3D(x + 1, y + 1, z) + Noise_3D(x - 1, y + 1, z) + Noise_3D(x, y + 1, z + 1) + Noise_3D(x, y + 1, z - 1) +
                       Noise_3D(x + 1, y - 1, z) + Noise_3D(x - 1, y - 1, z) + Noise_3D(x, y - 1, z + 1) + Noise_3D(x, y - 1, z - 1)) * 0.03125f;
        float corners = (Noise_3D(x + 1, y + 1, z + 1) + Noise_3D(x + 1, y + 1, z - 1) + Noise_3D(x - 1, y + 1, z + 1) + Noise_3D(x - 1, y + 1, z - 1) +
                         Noise_3D(x + 1, y - 1, z + 1) + Noise_3D(x - 1, y - 1, z - 1) + Noise_3D(x - 1, y - 1, z + 1) + Noise_3D(x - 1, y - 1, z - 1)) * 0.015625f;
        return center + faces + sides + corners;
    }

    private static float InterpolatedNoise_1D(float x)
    {
        int intergerX = (int)x;
        float fractional_X = x - intergerX;

        float v1 = Noise_1D(intergerX);
        float v2 = Noise_1D(intergerX + 1);

        return Linear_Interpolate_1D(v1, v2 , fractional_X);
    }
    private static float InterpolatedNoise_2D(float x, float y)
    {
        int interger_X = (int)x;
        float fractional_X = x - interger_X;

        int interger_Y = (int)y;
        float fractional_Y = y - interger_Y;

        //float v1 = SmoothNoise_2D(interger_X, interger_Y);
        //float v2 = SmoothNoise_2D(interger_X + 1, interger_Y);
        //float v3 = SmoothNoise_2D(interger_X, interger_Y + 1);
        //float v4 = SmoothNoise_2D(interger_X + 1, interger_Y + 1);
       
        float v1 = Noise_2D(interger_X, interger_Y);
        float v2 = Noise_2D(interger_X + 1, interger_Y);
        float v3 = Noise_2D(interger_X, interger_Y + 1);
        float v4 = Noise_2D(interger_X + 1, interger_Y + 1);

        //float i1 = Cosine_Interpolate_1D(v1, v2 , fractional_X);
        //float i2 = Cosine_Interpolate_1D(v3, v4 , fractional_X);

        //return Cosine_Interpolate_1D( i1, i2 , fractional_Y);

        return Cosine_Interpolate_2D(v1, v2, v3, v4, fractional_X, fractional_Y);
        //return Cosine_Interpolate_2D(v1, v2, v3, v4, fractional_X, fractional_Y);
    }
    public static float InterpolatedNoise_3D(float x, float y, float z)
    {
        int interger_X = (int)x;
        float fractional_X = x - interger_X;

        int interger_Y = (int)y;
        float fractional_Y = y - interger_Y;

        int interger_Z = (int)z;
        float fractional_Z = z - interger_Z;

        float c000 = Noise_3D(interger_X + 0, interger_Y + 0, interger_Z + 0);
        float c100 = Noise_3D(interger_X + 1, interger_Y + 0, interger_Z + 0);
        float c010 = Noise_3D(interger_X + 0, interger_Y + 1, interger_Z + 0);
        float c110 = Noise_3D(interger_X + 1, interger_Y + 1, interger_Z + 0);
        float c001 = Noise_3D(interger_X + 0, interger_Y + 0, interger_Z + 1);
        float c101 = Noise_3D(interger_X + 1, interger_Y + 0, interger_Z + 1);
        float c011 = Noise_3D(interger_X + 0, interger_Y + 1, interger_Z + 1);
        float c111 = Noise_3D(interger_X + 1, interger_Y + 1, interger_Z + 1);

        return Cosine_Interpolate_3D(c000, c100, c010, c110, c001, c101, c011, c111, fractional_X, fractional_Y, fractional_Z);
    }

    public static float Linear_Interpolate_3D(float c000, float c100, float c010, float c110, float c001, float c101, float c011, float c111, float x, float y, float z)
    {
        return Linear_Interpolate_1D(Linear_Interpolate_2D(c000, c100, c010, c110, x, y), Linear_Interpolate_2D(c001, c101, c011, c111, x, y), z);
    }
    public static float Linear_Interpolate_2D(float c00, float c10, float c01, float c11, float x, float y)
    {
        return Linear_Interpolate_1D(Linear_Interpolate_1D(c00, c10, x), Linear_Interpolate_1D(c01, c11, x), y);
    }
    public static float Linear_Interpolate_1D(float c0, float c1, float x)
    {
        return (1 - x) * c0 + x * c1;
    }

    public static float PerlinNoise_1D(float x)
    {
        float total = 0;

        for (int i = 0; i < NUMBER_OF_OCTAVES; i++)
        {
            total += InterpolatedNoise_1D(x * (1 << i)) * (1 / (1 << i));
        }

        return total;
    }
    public static float PerlinNoise_2D(float x, float y)
    {
        float total = 0;

        int frequency = 0;
        float amplitude = 0;

        for (int i = 0; i < NUMBER_OF_OCTAVES; i++)
        {
            frequency = 1 << i;
            amplitude = 1f / frequency;
            total += InterpolatedNoise_2D(x * frequency, y * frequency) * amplitude;
        }

        return total;
    }
    public static float PerlinNoise_3D(float x, float y, float z)
    {
        float total = 0;

        int frequency = 0;
        float amplitude = 0;

        for (int i = 0; i < NUMBER_OF_OCTAVES; i++)
        {
            frequency = 1 << i; 
            amplitude = 1f / frequency;

            total += InterpolatedNoise_3D(x * frequency, y * frequency, z * frequency) * amplitude;
        }
        return total;
    }

    public static float PerlinNoise_2D(float x, float z, float scale, float amplitude, float exp)
    {
        return Mathf.Pow((PerlinNoise_2D(x / scale, z / scale) + 1) * amplitude, exp);
    }

    public static float PerlinNoise_3D(float x, float y, float z, float scale, float amplitude, float exp)
    {
        return Mathf.Pow((PerlinNoise_3D(x / scale, y / scale, z / scale) + 1) * amplitude, exp);
    }

    private static float Linear_Interpolate_1D(float[] p, float x)
    {
        return p[0] * (1 - x) + p[1] * x;
        
    }
    private static float Linear_Interpolate_2D(float[][] b, float x, float y)
    {
        return Linear_Interpolate_1D(new float[] { Linear_Interpolate_1D(b[0], x), Linear_Interpolate_1D(b[1], x) }, y);
    }
    private static float Linear_Interpolate_3D(float[][][] b, float x, float y, float z)
    {
        return Linear_Interpolate_1D(new float[] { Linear_Interpolate_2D(b[0], x, y), Linear_Interpolate_2D(b[1], x, y) }, z);
    }

    private static float Cosine_Interpolate_1D(float p0, float p1, float x)
    {
        float x2 = (1 - MathExtension.Cos(x * Mathf.PI)) * 0.5f;
        return p0 * (1 - x2) + p1 * x2;

    }
    private static float Cosine_Interpolate_1D(float[] p, float x)
    {
        float x2 = (1 - Mathf.Cos(x * Mathf.PI)) * 0.5f;
        return p[0] * (1 - x2) + p[1] * x2;

    }
    private static float Cosine_Interpolate_2D(float[][] b, float x, float y)
    {
        return Cosine_Interpolate_1D(new float[] { Cosine_Interpolate_1D(b[0], x), Cosine_Interpolate_1D(b[1], x) }, y);
    }
    private static float Cosine_Interpolate_2D(float p00, float p10, float p01, float p11, float x, float y)
    {
        return Cosine_Interpolate_1D(Cosine_Interpolate_1D(p00, p10, x), Cosine_Interpolate_1D(p01, p11, x), y);
    }
    private static float Cosine_Interpolate_3D(float[][][] b, float x, float y, float z)
    {
        return Cosine_Interpolate_1D(new float[] { Cosine_Interpolate_2D(b[0], x, y), Cosine_Interpolate_2D(b[1], x, y) }, z);
    }
    public static float Cosine_Interpolate_3D(float p000, float p100, float p010, float p110, float p001, float p101, float p011, float p111, float x, float y, float z)
    {
        return Cosine_Interpolate_1D(Cosine_Interpolate_2D(p000, p100, p010, p110, x, y), Cosine_Interpolate_2D(p001, p101, p011, p111, x, y), z);
    }

    private static float Cubic_Interpolate_1D(float[] p, float x)
    {
        return p[1] + 0.5f * x * (p[2] - p[0] + x * (2.0f * p[0] - 5.0f * p[1] + 4.0f * p[2] - p[3] + x * (3.0f * (p[1] - p[2]) + p[3] - p[0])));
    }
    private static float Cubic_Interpolate_2D(float[][] p, float x, float y)
    {
        return Cubic_Interpolate_1D(new float[] {Cubic_Interpolate_1D(p[0], x),
                                                 Cubic_Interpolate_1D(p[1], x),
                                                 Cubic_Interpolate_1D(p[2], x),
                                                 Cubic_Interpolate_1D(p[3], x)},
                                                 y);
    }
    private static float Cubic_Interpolate_3D(float[][][] p, float x, float y, float z)
    {
        return Cubic_Interpolate_1D(new float[] { Cubic_Interpolate_2D(p[0], x, y),
                                                  Cubic_Interpolate_2D(p[1], x, y),
                                                  Cubic_Interpolate_2D(p[2], x, y),
                                                  Cubic_Interpolate_2D(p[3], x, y),},
                                                  z);
    }
}
